package com.cgt.xiaotao.promotion.service.impl;

import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.date.LocalDateTimeUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cgt.common.constants.MqConstants;
import com.cgt.common.domain.dto.PageDTO;
import com.cgt.common.exception.BadRequestException;
import com.cgt.common.exception.BizIllegalException;
import com.cgt.common.mq.RabbitMqHelper;
import com.cgt.common.utils.*;
import com.cgt.xiaotao.promotion.constants.PromotionConstants;
import com.cgt.xiaotao.promotion.discount.Discount;
import com.cgt.xiaotao.promotion.discount.DiscountStrategy;
import com.cgt.xiaotao.promotion.domain.dto.CouponDiscountDTO;
import com.cgt.xiaotao.promotion.domain.dto.OrderProductDTO;
import com.cgt.xiaotao.promotion.domain.dto.UserCouponDTO;
import com.cgt.xiaotao.promotion.domain.po.Coupon;
import com.cgt.xiaotao.promotion.domain.po.CouponScope;
import com.cgt.xiaotao.promotion.domain.po.UserCoupon;
import com.cgt.xiaotao.promotion.domain.po.ExchangeCode;
import com.cgt.xiaotao.promotion.domain.query.UserCouponQuery;
import com.cgt.xiaotao.promotion.domain.vo.CouponVO;
import com.cgt.xiaotao.promotion.enums.ExchangeCodeStatus;
import com.cgt.xiaotao.promotion.enums.UserCouponStatus;
import com.cgt.xiaotao.promotion.mapper.CouponMapper;
import com.cgt.xiaotao.promotion.mapper.UserCouponMapper;
import com.cgt.xiaotao.promotion.service.ICouponScopeService;
import com.cgt.xiaotao.promotion.service.IExchangeCodeService;
import com.cgt.xiaotao.promotion.service.IUserCouponService;
import com.cgt.xiaotao.promotion.utils.CodeUtil;
import com.cgt.xiaotao.promotion.utils.MyLock;
import com.cgt.xiaotao.promotion.utils.PermuteUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static com.cgt.xiaotao.promotion.constants.PromotionConstants.COUPON_CODE_MAP_KEY;
import static com.cgt.xiaotao.promotion.constants.PromotionConstants.COUPON_RANGE_KEY;

@Slf4j
@Service
@RequiredArgsConstructor
public class UserCouponServiceImpl extends ServiceImpl<UserCouponMapper, UserCoupon> implements IUserCouponService {

    private final CouponMapper couponMapper;
    private final IExchangeCodeService codeService;
    private final StringRedisTemplate redisTemplate;
    private final RedissonClient redissonClient;
    private final RabbitMqHelper mqHelper;
    private final ICouponScopeService scopeService;

    private static final RedisScript<Long> RECEIVE_COUPON_SCRIPT;
    private static final RedisScript<String> EXCHANGE_COUPON_SCRIPT;

    static {
        try {
            RECEIVE_COUPON_SCRIPT = RedisScript.of(new ClassPathResource("lua/receive_coupon.lua"), Long.class);
            EXCHANGE_COUPON_SCRIPT = RedisScript.of(new ClassPathResource("lua/exchange_coupon.lua"), String.class);
        } catch (Exception e) {
            throw new RuntimeException("加载lua脚本失败", e);
        }
    }

    @Override
    //@MyLock(name = "lock:coupon:#{couponId}")
    public void receiveCoupon(String couponIdS) {
        // 0.将String类型的优惠券id转为Long
        Long couponId = NumberUtils.parseLong(couponIdS);

        // 1.执行LUA脚本，判断结果
        // 1.1.准备参数
        String key1 = PromotionConstants.COUPON_CACHE_KEY_PREFIX + couponId;
        String key2 = PromotionConstants.USER_COUPON_CACHE_KEY_PREFIX + couponId;
        Long userId = UserContext.getUser();
        // 1.2.执行脚本
        Long r = redisTemplate.execute(RECEIVE_COUPON_SCRIPT, Arrays.asList(key1, key2), userId.toString());
        int result = NumberUtils.null2Zero(r).intValue();
        if (result != 0) {
            // 结果大于0，说明出现异常
            throw new BizIllegalException(PromotionConstants.RECEIVE_COUPON_ERROR_MSG[result - 1]);
        }
        // 6.发送MQ消息
        UserCouponDTO uc = new UserCouponDTO();
        uc.setUserId(userId);
        uc.setCouponId(couponId);
        mqHelper.send(MqConstants.Exchange.PROMOTION_EXCHANGE, MqConstants.Key.COUPON_RECEIVE, uc);

        /*// 1.查询优惠券
        Coupon coupon = queryCouponByCache(couponId);
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在");
        }
        // 2.校验发放时间
        LocalDateTime now = LocalDateTime.now();
        if (coupon.getIssueBeginTime() == null || coupon.getIssueEndTime() == null ||
                now.isBefore(coupon.getIssueBeginTime()) || now.isAfter(coupon.getIssueEndTime())) {
            throw new BadRequestException("优惠券发放已经结束或尚未开始");
        }

        // 3.校验库存
        if (coupon.getIssueNum() >= coupon.getTotalNum()) {
            throw new BadRequestException("优惠券库存不足");
        }
        Long userId = UserContext.getUser();
        // 4.校验每人限领数量
        // 4.1.查询领取数量
        String key = PromotionConstants.USER_COUPON_CACHE_KEY_PREFIX + couponId;
        Long count = redisTemplate.opsForHash().increment(key, userId.toString(), 1);
        // 4.2.校验限领数量
        if(count > coupon.getUserLimit()){
            throw new BadRequestException("超出领取数量");
        }
        // 5.扣减优惠券库存
        redisTemplate.opsForHash().increment(
                PromotionConstants.COUPON_CACHE_KEY_PREFIX + couponId, "totalNum", -1);

        // 6.发送MQ消息
        UserCouponDTO uc = new UserCouponDTO();
        uc.setUserId(userId);
        uc.setCouponId(couponId);
        mqHelper.send(MqConstants.Exchange.PROMOTION_EXCHANGE, MqConstants.Key.COUPON_RECEIVE, uc);*/

    }

    //利用lua脚本不要锁也是线程安全
    @Override
    public void exchangeCoupon(String code) {
        // 1.校验并解析兑换码
        long serialNum = CodeUtil.parseCode(code);
        // 2.执行LUA脚本
        Long userId = UserContext.getUser();
        String result = redisTemplate.execute(
                EXCHANGE_COUPON_SCRIPT,
                Arrays.asList(COUPON_CODE_MAP_KEY, COUPON_RANGE_KEY),
                String.valueOf(serialNum), String.valueOf(serialNum + 5000), userId.toString());
        long r = NumberUtils.parseLong(result);
        if (r < 10) {
            // 异常结果应该是在1~5之间
            throw new BizIllegalException(PromotionConstants.EXCHANGE_COUPON_ERROR_MSG[(int) (r - 1)]);
        }
        // 3.发送MQ消息通知
        UserCouponDTO uc = new UserCouponDTO();
        uc.setUserId(userId);
        uc.setCouponId(r);
        uc.setSerialNum((int) serialNum);
        mqHelper.send(MqConstants.Exchange.PROMOTION_EXCHANGE, MqConstants.Key.COUPON_RECEIVE, uc);
    }

    @Override
    public PageDTO<CouponVO> queryMyCouponPage(UserCouponQuery query) {
        // 1.获取当前用户
        Long userId = UserContext.getUser();
        // 2.分页查询用户券
        Page<UserCoupon> page = lambdaQuery()
                .eq(UserCoupon::getUserId, userId)
                .eq(UserCoupon::getStatus, query.getStatus())
                .page(query.toMpPage(new OrderItem("term_end_time", true)));
        List<UserCoupon> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }

        // 3.获取优惠券详细信息
        // 3.1.获取用户券关联的优惠券id
        Set<Long> couponIds = records.stream().map(UserCoupon::getCouponId).collect(Collectors.toSet());
        // 3.2.查询
        List<Coupon> coupons = couponMapper.selectBatchIds(couponIds);

        // 4.封装VO
        return PageDTO.of(page, BeanUtils.copyList(coupons, CouponVO.class));
    }

    @Override
    @Transactional
    public void checkAndCreateUserCoupon(UserCouponDTO uc) {
        //1.查询优惠券信息
        Coupon coupon = couponMapper.selectById(uc.getCouponId());
        if (coupon == null) {
            return;
        }

        // 2.更新优惠券的已经发放的数量 + 1
        int num = couponMapper.incrIssueNum(coupon.getId());
        if (num == 0) {
            return;
        }
        // 3.新增一个用户券
        saveUserCoupon(coupon, uc.getUserId());
        // 4.更新兑换码状态
        if (uc.getSerialNum() != null) {
            codeService.lambdaUpdate()
                    .set(ExchangeCode::getUserId, uc.getUserId())
                    .set(ExchangeCode::getStatus, ExchangeCodeStatus.USED)
                    .eq(ExchangeCode::getId, uc.getSerialNum())
                    .update();
        }
    }

    //保存用户券
    private void saveUserCoupon(Coupon coupon, Long userId) {
        // 1.基本信息
        UserCoupon uc = new UserCoupon();
        uc.setUserId(userId);
        uc.setCouponId(coupon.getId());
        // 2.有效期信息
        LocalDateTime termBeginTime = coupon.getTermBeginTime();
        LocalDateTime termEndTime = coupon.getTermEndTime();
        if (termBeginTime == null) {
            termBeginTime = LocalDateTime.now();
            termEndTime = termBeginTime.plusDays(coupon.getTermDays());
        }
        uc.setTermBeginTime(termBeginTime);
        uc.setTermEndTime(termEndTime);
        // 3.保存
        save(uc);
    }

    private Coupon queryCouponByCache(Long couponId) {
        // 1.准备KEY
        String key = PromotionConstants.COUPON_CACHE_KEY_PREFIX + couponId;
        // 2.查询
        Map<Object, Object> objMap = redisTemplate.opsForHash().entries(key);
        if (objMap.isEmpty()) {
            return null;
        }
        // 3.数据反序列化
        CopyOptions copyOptions = CopyOptions.create()
                .setIgnoreError(true)
                .setFieldValueEditor((key1, value) -> {
                    // 处理时间戳格式的日期字段
                    if (value instanceof String && isNumeric((String) value) &&
                            ("issueBeginTime".equals(key1) || "issueEndTime".equals(key1) ||
                                    "termBeginTime".equals(key1) || "termEndTime".equals(key1))) {
                        try {
                            long timestamp = Long.parseLong((String) value);
                            return LocalDateTimeUtil.of(timestamp);
                        } catch (Exception e) {
                            return value;
                        }
                    }
                    return value;
                });
        return BeanUtils.mapToBean(objMap, Coupon.class, false, copyOptions);
    }

    /**
     * 判断字符串是否为纯数字
     *
     * @param str 字符串
     * @return 是否为数字
     */
    private boolean isNumeric(String str) {
        if (str == null || str.isEmpty()) {
            return false;
        }
        for (int i = 0; i < str.length(); i++) {
            if (!Character.isDigit(str.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    @Override
    public List<CouponDiscountDTO> findDiscountSolution(List<OrderProductDTO> products) {
        // 生成缓存key
        Long userId = UserContext.getUser();
        String cacheKey = "coupon:solution:" + userId + ":" + products.hashCode();

        // 尝试从Redis获取缓存结果
        String cachedResult = redisTemplate.opsForValue().get(cacheKey);
        if (StringUtils.isNotBlank(cachedResult)) {
            try {
                // 反序列化缓存结果
                return JSON.parseArray(cachedResult, CouponDiscountDTO.class);
            } catch (Exception e) {
                log.error("反序列化优惠券方案失败", e);
            }
        }

        //1.查询当前用户可用券 coupon和use_coupon表，条件userid，status=1，查优惠券规则，优惠券id和用户券id
        List<Coupon> coupons = getBaseMapper().queryMyCoupon(userId);
        if (CollUtils.isEmpty(coupons)) {
            // 将空结果缓存1分钟
            redisTemplate.opsForValue().set(cacheKey, "[]", Duration.ofMinutes(1));
            return CollUtils.emptyList();
        }

        //2.初筛
        //2.1计算订单的总金额 price累加
        int totalAmount = products.stream()
                .mapToInt(p -> p.getPrice().multiply(BigDecimal.valueOf(p.getQuantity())).intValue())
                .sum();

        //2.2校验优惠券是否可用
        List<Coupon> availableCoupons = new ArrayList<>();
        for (Coupon coupon : coupons) {
            boolean flag = DiscountStrategy.getDiscount(coupon.getDiscountType()).canUse(totalAmount, coupon);
            if (flag) {
                availableCoupons.add(coupon);
            }
        }
        if (CollUtils.isEmpty(availableCoupons)) {
            // 将空结果缓存1分钟
            redisTemplate.opsForValue().set(cacheKey, "[]", Duration.ofMinutes(1));
            return CollUtils.emptyList();
        }

        //3.细筛，考虑优惠券限定范围排列组合
        Map<Coupon, List<OrderProductDTO>> avamap = findAvailableCoupons(availableCoupons, products);
        if (avamap.isEmpty()) {
            // 将空结果缓存1分钟
            redisTemplate.opsForValue().set(cacheKey, "[]", Duration.ofMinutes(1));
            return CollUtils.emptyList();
        }
        // 3.2.排列组合
        availableCoupons = new ArrayList<>(avamap.keySet());
        List<List<Coupon>> solutions = PermuteUtil.permute(availableCoupons);
        // 3.3.添加单券的方案
        for (Coupon c : availableCoupons) {
            solutions.add(Collections.singletonList(c));
        }

        // 4.计算方案的优惠明细（改为同步计算）
        List<CouponDiscountDTO> list = new ArrayList<>(solutions.size());
        for (List<Coupon> solution : solutions) {
            // 4.2.同步计算
            CouponDiscountDTO dto = calculateSolutionDiscount(avamap, products, solution);
            // 4.3.添加结果
            list.add(dto);
        }

        // 5.筛选最优解
        List<CouponDiscountDTO> result = findBestSolution(list);

        if (CollUtils.isNotEmpty(result)) {
            // 6.将结果缓存到Redis中，缓存10分钟
            try {
                String jsonResult = com.alibaba.fastjson.JSON.toJSONString(result);
                redisTemplate.opsForValue().set(cacheKey, jsonResult, Duration.ofMinutes(10));
            } catch (Exception e) {
                log.error("序列化优惠券方案失败", e);
            }
        }

        return result;
    }

    //细筛查询每一个优惠券对应的商品
    private Map<Coupon, List<OrderProductDTO>> findAvailableCoupons(
            List<Coupon> coupons, List<OrderProductDTO> products) {
        Map<Coupon, List<OrderProductDTO>> map = new HashMap<>(coupons.size());

        // 由于所有优惠券都没有限定范围，所以所有商品都可用
        for (Coupon coupon : coupons) {
            // 计算商品总价
            int totalAmount = products.stream()
                    .mapToInt(p -> p.getPrice().multiply(BigDecimal.valueOf(p.getQuantity())).intValue())
                    .sum();
            // 判断是否可用
            Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());
            if (discount.canUse(totalAmount, coupon)) {
                map.put(coupon, products);
            }
        }
        return map;
    }


    //计算折扣信息
    private void calculateDiscountDetails(Map<Long, Integer> detailMap, List<OrderProductDTO> products,
                                          int totalAmount, int discountAmount) {
        int times = 0;
        int remainDiscount = discountAmount;
        for (OrderProductDTO product : products) {
            // 更新课程已计算数量
            times++;
            int discount = 0;
            // 判断是否是最后一个课程
            if (times == products.size()) {
                // 是最后一个课程，总折扣金额 - 之前所有商品的折扣金额之和
                discount = remainDiscount;
            } else {
                // 计算折扣明细（课程价格在总价中占的比例，乘以总的折扣）
                discount = discountAmount * product.getPrice().multiply(BigDecimal.valueOf(product.getQuantity())).intValue() / totalAmount;
                remainDiscount -= discount;
            }
            // 更新折扣明细
            detailMap.put(product.getProductId(), discount + detailMap.get(product.getProductId()));
        }
    }
    //计算优惠信息
    private CouponDiscountDTO calculateSolutionDiscount(Map<Coupon, List<OrderProductDTO>> avamap, List<OrderProductDTO> products, List<Coupon> solutions) {
        //1.创建方案结果dto
        CouponDiscountDTO dto = new CouponDiscountDTO();
        // 2.初始化折扣明细的映射
        Map<Long, Integer> detailMap = products.stream().collect(Collectors.toMap(OrderProductDTO::getProductId, oc -> 0));
        // 3.计算折扣
        for (Coupon coupon : solutions) {
            // 3.1.获取优惠券对应的全部商品（由于没有限定范围，所有商品都可用）
            List<OrderProductDTO> availableProducts = avamap.get(coupon);
            // 3.2.计算商品总价(商品原价*数量 - 折扣明细)
            int totalAmount = availableProducts.stream()
                    .mapToInt(oc -> oc.getPrice().multiply(BigDecimal.valueOf(oc.getQuantity())).intValue() - detailMap.get(oc.getProductId()))
                    .sum();
            // 3.3.判断是否可用
            Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());
            if (!discount.canUse(totalAmount, coupon)) {
                // 券不可用，跳过
                continue;
            }
            // 3.4.计算优惠金额
            int discountAmount = discount.calculateDiscount(totalAmount, coupon);
            // 3.5.计算优惠明细
            calculateDiscountDetails(detailMap, availableProducts, totalAmount, discountAmount);
            // 3.6.更新DTO数据
            dto.getIds().add(coupon.getId());
            dto.getRules().add(discount.getRule(coupon));
            dto.setDiscountAmount(discountAmount + dto.getDiscountAmount());
        }
        return dto;
    }

    //求最优解
    private List<CouponDiscountDTO> findBestSolution(List<CouponDiscountDTO> list) {
        // 1.准备Map记录最优解
        Map<String, CouponDiscountDTO> moreDiscountMap = new HashMap<>();
        Map<Integer, CouponDiscountDTO> lessCouponMap = new HashMap<>();
        // 2.遍历，筛选最优解
        for (CouponDiscountDTO solution : list) {
            // 2.1.计算当前方案的id组合
            String ids = solution.getIds().stream()
                    .sorted(Long::compare).map(String::valueOf).collect(Collectors.joining(","));
            // 2.2.比较用券相同时，优惠金额是否最大
            CouponDiscountDTO best = moreDiscountMap.get(ids);
            if (best != null && best.getDiscountAmount() >= solution.getDiscountAmount()) {
                // 当前方案优惠金额少，跳过
                continue;
            }
            // 2.3.比较金额相同时，用券数量是否最少
            best = lessCouponMap.get(solution.getDiscountAmount());
            int size = solution.getIds().size();
            if (size > 1 && best != null && best.getIds().size() <= size) {
                // 当前方案用券更多，放弃
                continue;
            }
            // 2.4.更新最优解
            moreDiscountMap.put(ids, solution);
            lessCouponMap.put(solution.getDiscountAmount(), solution);
        }
        // 3.求交集
        Collection<CouponDiscountDTO> bestSolutions = CollUtils
                .intersection(moreDiscountMap.values(), lessCouponMap.values());
        // 4.排序，按优惠金额降序
        return bestSolutions.stream()
                .sorted(Comparator.comparingInt(CouponDiscountDTO::getDiscountAmount).reversed())
                .collect(Collectors.toList());
    }


    // 根据优惠券id列表和商品列表获取最大优惠金额
    @Override
    public BigDecimal getMaxDiscountAmount(List<Long> couponIds, List<OrderProductDTO> products) {
        // 1. 生成缓存key
        Long userId = UserContext.getUser();
        String cacheKey = "coupon:solution:" + userId + ":" + products.hashCode();

        // 2. 尝试从Redis获取缓存结果
        List<CouponDiscountDTO> solutions = null;
        String cachedResult = redisTemplate.opsForValue().get(cacheKey);
        if (StringUtils.isNotBlank(cachedResult)) {
            try {
                // 反序列化缓存结果
                solutions = JSON.parseArray(cachedResult, CouponDiscountDTO.class);
            } catch (Exception e) {
                log.error("反序列化优惠券方案失败", e);
            }
        }

        // 3. 如果缓存中没有，则调用findDiscountSolution方法获取最优优惠方案
        if (solutions == null || solutions.isEmpty()) {
            solutions = findDiscountSolution(products);
        }

        System.out.println("solutions" + solutions);

        // 4. 如果没有可用方案，返回0
        if (CollUtils.isEmpty(solutions)) {
            return BigDecimal.ZERO;
        }

        // 5. 将传入的couponIds转换为Set以提高查找效率
        Set<Long> targetCouponIds = new HashSet<>(couponIds);

        // 6. 检查是否有与传入的couponIds匹配的方案
        for (CouponDiscountDTO solution : solutions) {
            // 判断该方案是否与传入的优惠券ID列表完全一致（不考虑顺序）
            Set<Long> solutionIds = new HashSet<>(solution.getIds());
            if (solutionIds.size() == targetCouponIds.size() && solutionIds.equals(targetCouponIds)) {
                // 找到匹配的方案，返回该方案的折扣金额
                System.out.println("找到匹配的方案，返回该方案的折扣金额" + solution.getDiscountAmount());
                return BigDecimal.valueOf(solution.getDiscountAmount());
            }
        }

        // 7. 如果没有匹配的方案，返回0
        return BigDecimal.ZERO;
    }

    /**
     * 清除用户的优惠券方案缓存
     * @param userId 用户ID
     * @param products 商品列表
     */
    public void clearCouponSolutionCache(Long userId, List<OrderProductDTO> products) {
        String cacheKey = "coupon:solution:" + userId + ":" + products.hashCode();
        redisTemplate.delete(cacheKey);
    }




    /**
     * 删除用户优惠券
     * @param id 优惠券ID
     * @return 是否成功
     */
    @Override
    public Boolean deleteUserCoupon(Long id) {
        Long userId = UserContext.getUser();
        UserCoupon userCoupon = this.lambdaQuery().eq(UserCoupon::getUserId, userId).eq(UserCoupon::getCouponId, id).one();
        if (userCoupon == null) {
            return false;
        }
        // 检查优惠券状态，只有未使用的优惠券才能被"使用"
        if (userCoupon.getStatus() != UserCouponStatus.UNUSED) {
            return false;
        }
        // 更新优惠券状态为已使用
        userCoupon.setStatus(UserCouponStatus.USED);
        userCoupon.setUsedTime(LocalDateTime.now());
        return this.updateById(userCoupon);
    }
}
